package com.zh.webcommon.common.auth;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.BiConsumer;

/**
 * 扫描全部的接口的请求URI
 * 只扫描被@RestController注解的类
 *
 * @author ZH
 * @date 13:38 2021/11/4
 */
@Slf4j
public class CollectRequestURIScanner {
    /**
     * 查找的钩子方法
     */
    private BiConsumer<RequestInfo, Method> lookfor = (info, method) -> {
    };
    private final String packageName;

    public CollectRequestURIScanner(String packageName) {
        this.packageName = packageName;
    }

    @SneakyThrows
    public void scanner() {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(String.format("classpath*:%s/**/*.class", packageName.replace(".", "/")));
        Arrays.stream(resources).forEach(e -> {
            try {
                log.trace("[scanner class] resource={} ： uri = {}", e.getFilename(), e.getURI());
                Class<?> clazz = Class.forName(extractClassName(packageName, e.getURI().toString()));
                RestController restContr = clazz.getDeclaredAnnotation(RestController.class);
                if (restContr != null) {
                    Class<?> controller = clazz;
                    RequestMapping requestPrefix = controller.getDeclaredAnnotation(RequestMapping.class);
                    String requestUriPrefix = (requestPrefix == null || requestPrefix.value().length == 0)
                            ? "" : requestPrefix.value()[0];

                    Method[] declaredMethods = controller.getDeclaredMethods();

                    for (Method method : declaredMethods) {
                        final RequestInfo requestInfo = new RequestInfo(requestUriPrefix);
                        if (methodIsRequestMapping(method, requestInfo)) {
                            log.debug("requestInfo：{}", requestInfo);

                            lookfor.accept(requestInfo, method);
                        }
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        });
    }

    /**
     * 寻找的钩子方法
     *
     * @param fun
     */
    public void setLookFor(BiConsumer<RequestInfo, Method> fun) {
        this.lookfor = fun;
    }

    private static boolean methodIsRequestMapping(Method method, final RequestInfo requestInfo) {
        boolean isRequestMapping = method.isAnnotationPresent(RequestMapping.class);
        if (isRequestMapping) {
            RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
            return requestInfo.populate(requestMapping.value().length == 0 ? "" : requestMapping.value()[0], requestMapping.method());
        }

        boolean isGetRequest = method.isAnnotationPresent(GetMapping.class);
        if (isGetRequest) {
            GetMapping getMapping = method.getDeclaredAnnotation(GetMapping.class);
            return requestInfo.populate(getMapping.value().length == 0 ? "" : getMapping.value()[0], RequestMethod.GET);
        }

        boolean isPutRequest = method.isAnnotationPresent(PutMapping.class);
        if (isPutRequest) {
            PutMapping putMapping = method.getDeclaredAnnotation(PutMapping.class);
            return requestInfo.populate(putMapping.value().length == 0 ? "" : putMapping.value()[0], RequestMethod.PUT);
        }

        boolean isPostRequest = method.isAnnotationPresent(PostMapping.class);
        if (isPostRequest) {
            PostMapping postMapping = method.getDeclaredAnnotation(PostMapping.class);
            return requestInfo.populate(postMapping.value().length == 0 ? "" : postMapping.value()[0], RequestMethod.POST);
        }

        boolean isDeleteRequest = method.isAnnotationPresent(DeleteMapping.class);
        if (isDeleteRequest) {
            DeleteMapping deleteMapping = method.getDeclaredAnnotation(DeleteMapping.class);
            return requestInfo.populate(deleteMapping.value().length == 0 ? "" : deleteMapping.value()[0], RequestMethod.DELETE);
        }

        return false;
    }

    private static String extractClassName(String packageName, String path) {
        String basePath = packageName.replace(".", "/");
        String relativePath = path.substring(path.indexOf(basePath));
        int endIndex = relativePath.length() - ".class".length();
        return relativePath.substring(0, endIndex).replace("/", ".");
    }

    /**
     * 封装请求的URL数据
     */
    public static class RequestInfo {
        private final String uriPrefix;
        private String value;
        private RequestMethod[] methods;

        private RequestInfo() {
            this.uriPrefix = "";
            this.value = "";
        }

        private RequestInfo(String requestUriPrefix) {
            if (requestUriPrefix.isEmpty() || requestUriPrefix.equals("/")) {
                this.uriPrefix = "";
            } else {
                if (requestUriPrefix.endsWith("/")) {
                    requestUriPrefix = requestUriPrefix.substring(0, requestUriPrefix.length() - 1);
                }

                this.uriPrefix = requestUriPrefix.startsWith("/") ? requestUriPrefix : "/" + requestUriPrefix;
            }
        }

        private boolean populate(String value, RequestMethod... methods) {
            var valueStr = value.startsWith("/") ? value : "/" + value;
            this.value = valueStr.replaceAll("(\\{\\w+\\})", "*");
            this.methods = methods;
            return true;
        }

        public String getRequestURI() {
            return uriPrefix + value;
        }

        public RequestMethod[] getMethods() {
            return methods;
        }

        @Override
        public String toString() {
            return "RequestInfo{" +
                    "uriPrefix='" + uriPrefix + '\'' +
                    ", value='" + value + '\'' +
                    ", methods=" + Arrays.toString(methods) +
                    '}';
        }
    }

}
